import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, Logger, Req } from '@nestjs/common'
import { Like, Repository } from 'typeorm'
import { InjectRepository } from '@nestjs/typeorm'
import { BcryptPassword, formatDate, omit } from 'src/utils'
import { RedisService } from 'src/redis/redis.service'
import { JwtService } from '@nestjs/jwt'
import { ConfigService } from '@nestjs/config'
import { Menu } from 'src/modules/menu/entities/menu.entity'
import { Role } from 'src/modules/role/entities/role.entity'
import { Permission } from 'src/modules/permission/entities/permission.entity'
import { Request } from 'express'
import { RegisterUserDto } from './dto/register-user.dto'
import { User } from './entities/user.entity'
import { History } from './entities/history.entity'
import { LoginUserDto } from './dto/login-user.dto'
import { LoginUserVo } from './vo/login-user.vo'
import { UpdateUserPasswordDto } from './dto/update-user-password.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { UserListVo } from './vo/user-list.vo'

@Injectable()
export class UserService {
  private logger = new Logger()

  @Inject(RedisService)
  private redisService: RedisService

  @Inject(JwtService)
  private jwtService: JwtService

  @Inject(ConfigService)
  private configService: ConfigService

  @InjectRepository(User)
  private userRepository: Repository<User>

  @InjectRepository(Role)
  private roleRepository: Repository<Role>

  @InjectRepository(Permission)
  private permissionRepository: Repository<Permission>

  @InjectRepository(Menu)
  private menuRepository: Repository<Menu>

  @InjectRepository(History)
  private loginHisitoryRepository: Repository<History>

  async initData() {
    const user1 = new User()
    user1.username = 'zhangsan'
    user1.password = await BcryptPassword.generate('111111')
    user1.email = 'xxx@xx.com'
    user1.isAdmin = true
    user1.nickName = '张三'
    user1.phoneNumber = '13233323333'

    const user2 = new User()
    user2.username = 'lisi'
    user2.password = await BcryptPassword.generate('222222')
    user2.email = 'yy@yy.com'
    user2.nickName = '李四'

    const role1 = new Role()
    role1.name = '管理员'
    role1.description = '管理员的描述'

    const role2 = new Role()
    role2.name = '普通用户'
    role2.description = '普通用户的描述'

    const permission1 = new Permission()
    permission1.code = 'ccc'
    permission1.description = '访问 ccc 接口'

    const permission2 = new Permission()
    permission2.code = 'ddd'
    permission2.description = '访问 ddd 接口'

    const menu1 = new Menu()
    menu1.name = '菜单11'
    menu1.path = '/demo11'
    menu1.redirect = '/demo11-redirect'
    menu1.metaTitle = 'demo11-meta'
    menu1.pid = 0

    const menu2 = new Menu()
    menu2.name = '菜单22'
    menu2.path = '/demo22'
    menu2.redirect = '/demo22-redirect'
    menu2.metaTitle = 'demo22-meta'
    menu2.pid = 1

    const menu3 = new Menu()
    menu3.name = '菜单33'
    menu3.path = '/demo33'
    menu3.redirect = '/demo33-redirect'
    menu3.metaTitle = 'demo33-meta'
    menu3.pid = 0

    user1.roles = [role1]
    user2.roles = [role2]

    role1.permissions = [permission1, permission2]
    role2.permissions = [permission1]

    role1.menu = [menu1, menu2, menu3]
    role2.menu = [menu1]

    await this.menuRepository.save([menu1, menu2, menu3])
    await this.permissionRepository.save([permission1, permission2])
    await this.roleRepository.save([role1, role2])
    await this.userRepository.save([user1, user2])
  }

  async register(user: RegisterUserDto) {
    const captcha = await this.redisService.get(`captcha_${user.email}`)

    // 验证码验证
    if (!captcha || typeof captcha !== 'string' || captcha.length !== 6) {
      throw new HttpException('验证码已失效或格式不正确', HttpStatus.BAD_REQUEST)
    }
    if (user.captcha !== captcha) {
      throw new HttpException('验证码不正确', HttpStatus.BAD_REQUEST)
    }

    // 关联角色
    const rolelist = await Promise.all(user.roles.map(async (name) => {
      const role = await this.roleRepository.findOne({ where: { name } })
      return role
    }))

    try {
      // 原子性: 事务中的所有操作要么全部成功执行，要么全部不执行。如果事务中的任何一步失败，整个事务将回滚，保证数据库状态不变。
      // 隔离性: 多个事务并发执行时，每个事务的操作对其他事务是不可见的，直到事务提交。这可以防止并发操作导致的数据不一致。
      await this.userRepository.manager.transaction(async (transactionalEntityManager) => {
        // 用户唯一性检查
        const foundUser = await transactionalEntityManager.findOne(User, { where: { username: user.username } })
        if (foundUser) {
          throw new HttpException('用户已存在', HttpStatus.BAD_REQUEST)
        }

        // 用户创建
        const newUser = new User()
        newUser.username = user.username
        newUser.password = await BcryptPassword.generate(user.password)
        newUser.email = user.email
        newUser.nickName = user.nickName
        newUser.roles = rolelist as any
        await transactionalEntityManager.save(newUser)
      })
      return '注册成功'
    }
    catch (e) {
      this.logger.error(`错误信息: ${e.message}`, UserService.name)
      throw new HttpException('注册失败，请稍后再试', HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async login(loginUserDto: LoginUserDto, isAdmin: boolean, req?: Request) {
    try {
      // 用户验证
      const user = await this.userRepository.findOne({
        where: {
          username: loginUserDto.username,
          // isAdmin, 暂时不区分管理端和客户端
        },
        relations: ['roles', 'roles.menu', 'roles.permissions'],
      })
      if (!user) {
        throw new HttpException('无效的用户名或密码', HttpStatus.BAD_REQUEST)
      }

      // 密码比对
      const passwordMatched = await BcryptPassword.compare(loginUserDto.password, user.password)
      if (!passwordMatched) {
        throw new HttpException('无效的用户名或密码', HttpStatus.BAD_REQUEST)
      }

      // 数据返回
      const vo = new LoginUserVo()
      vo.userInfo = {
        id: user.id,
        username: user.username,
        nickName: user.nickName,
        email: user.email,
        phoneNumber: user.phoneNumber,
        headPic: user.headPic,
        createTime: formatDate(user.createTime),
        updateTime: formatDate(user.updateTime),
        isFrozen: user.isFrozen,
        isAdmin: user.isAdmin,
        roles: user.roles.map(item => item.name),
        permissions: user.roles.reduce((arr, item) => {
          item.permissions.forEach((permission) => {
            const found = arr.find(p => p === permission.code)
            if (!found) {
              arr.push(permission.code)
            }
          })
          return arr
        }, []),
        menu: user.roles.reduce((arr, item) => {
          item.menu.forEach((menuItem) => {
            const found = arr.find(m => m.name === menuItem.name)
            if (!found) {
              arr.push({
                ...menuItem,
                createTime: formatDate(menuItem.createTime),
                updateTime: formatDate(menuItem.updateTime),
              })
            }
          })
          return arr
        }, []),
      }

      // 添加Token
      const data = await this.getJwtToken(vo.userInfo)
      vo.accessToken = data.accessToken
      vo.refreshToken = data.refreshToken

      this.createLoginHisitory(user, req)

      return vo
    }
    catch (e) {
      if (e instanceof HttpException) {
        // 直接抛出上面的信息
        throw e
      }
      else {
        // 其他错误，记录日志并抛出通用的 HttpException
        this.logger.error(`错误信息: ${e.message}`, UserService.name)
        throw new HttpException('登录失败，请稍后再试', HttpStatus.INTERNAL_SERVER_ERROR)
      }
    }
  }

  async logout(userId: number) {
    await this.redisService.del(`valid_user_token_${userId}`)
    return '退出成功'
  }

  async createLoginHisitory(user: any, req: Request) {
    try {
      const history = new History()
      history.username = user.username
      history.ip = req.ip
      history.host = req.headers.host
      await this.loginHisitoryRepository.save(history)
    }
    catch (e) {
      this.logger.error(`错误信息: ${e.message}`, UserService.name)
      throw new HttpException('登录历史记录失败', HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async getLoginHisitory(pageNo: number = 1, pageSize: number = 10, name: string) {
    // 边界条件检查
    if (pageNo <= 0 || pageSize <= 0) {
      throw new BadRequestException('页码和页数最小为 1')
    }

    const skipCount = (pageNo - 1) * pageSize
    const condition: Record<string, any> = {}
    if (name) {
      condition.name = Like(`%${name}%`)
    }

    const [loginList, totalCount] = await this.loginHisitoryRepository.findAndCount({
      skip: skipCount,
      take: pageSize,
      where: condition,
    })

    const list = loginList.map((menu) => {
      return {
        ...menu,
        createTime: formatDate(menu.createTime),
      }
    })

    return {
      loginList: list,
      totalCount,
    }
  }

  async getJwtToken(user: any) {
    const accessToken = this.jwtService.sign({
      userId: user.id,
      username: user.username,
      email: user.email,
      roles: user.roles,
    }, {
      expiresIn: this.configService.get('jwt_access_token_expires_time') || '30m',
    })

    const refreshToken = this.jwtService.sign({
      userId: user.id,
    }, {
      expiresIn: this.configService.get('jwt_refresh_token_expires_time') || '7d',
    })

    // 设置用户的过期时间(半个小时)
    await this.redisService.set(`valid_user_token_${user.id}`, accessToken, this.configService.get('redis_valid_token_expire_time') || 30 * 60)

    return { accessToken, refreshToken }
  }

  async findUserById(userId: number) {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
        // isAdmin,
      },
      relations: ['roles', 'roles.permissions'],
    })

    return {
      id: user.id,
      username: user.username,
      email: user.email,
      isAdmin: user.isAdmin,
      createTime: formatDate(user.createTime),
      updateTime: formatDate(user.updateTime),
      roles: user.roles.map(item => item.name),
      permissions: user.roles.reduce((arr, item) => {
        item.permissions.forEach((permission) => {
          const found = arr.find(p => p.code === permission.code)
          if (!found) {
            arr.push(permission)
          }
        })
        return arr
      }, []),
    }
  }

  async getUserInfoById(userId: number) {
    const user = await this.userRepository.findOne({
      where: {
        id: userId,
      },
      relations: ['roles', 'roles.menu', 'roles.permissions'],
    })

    const data = omit(user, ['password'])

    return {
      id: data.id,
      username: data.username,
      email: data.email,
      isAdmin: data.isAdmin,
      headPic: data.headPic,
      phoneNumber: data.phoneNumber,
      createTime: formatDate(data.createTime),
      updateTime: formatDate(data.updateTime),
      roles: data.roles.map(item => item.name),
      menu: data.roles.reduce((arr, item) => {
        item.menu.forEach((menuItem) => {
          const found = arr.find(m => m.name === menuItem.name)
          if (!found) {
            arr.push({
              ...menuItem,
              createTime: formatDate(menuItem.createTime),
              updateTime: formatDate(menuItem.updateTime),
            })
          }
        })
        return arr
      }, []),
      permissions: data.roles.reduce((arr, item) => {
        item.permissions.forEach((permission) => {
          const found = arr.find(p => p.code === permission.code)
          if (!found) {
            arr.push(permission)
          }
        })
        return arr
      }, []),
    }
  }

  async freezeUserById(id: number) {
    const user = await this.userRepository.findOneBy({ id })

    user.isFrozen = !user.isFrozen

    await this.userRepository.save(user)
  }

  async updateUserInfoById(userId: number, updateUserDto: UpdateUserDto) {
    const captcha = await this.redisService.get(`update_user_captcha_${updateUserDto.email}`)

    // 验证码验证
    if (!captcha || typeof captcha !== 'string' || captcha.length !== 6) {
      throw new HttpException('验证码已失效', HttpStatus.BAD_REQUEST)
    }
    if (updateUserDto.captcha !== captcha) {
      throw new HttpException('验证码不正确', HttpStatus.BAD_REQUEST)
    }

    // 用户验证
    const foundUser = await this.userRepository.findOneBy({ id: userId })
    if (!foundUser) {
      throw new HttpException('用户不存在', HttpStatus.BAD_REQUEST)
    }

    // 关联角色
    const rolelist = await Promise.all(updateUserDto.roles.map(async (name: any) => {
      const role = await this.roleRepository.findOne({ where: { name } })
      return role
    }))
    updateUserDto.roles = rolelist

    // 更新用户信息
    Object.assign(foundUser, updateUserDto)

    // 保存
    try {
      await this.userRepository.save(foundUser)
      return '用户信息修改成功'
    }
    catch (e) {
      if (e instanceof HttpException) {
        // 直接抛出上面的信息
        throw e
      }
      else {
        // 其他错误，记录日志并抛出通用的 HttpException
        this.logger.error(`错误信息: ${e.message}`, UserService.name)
        throw new HttpException('用户信息修改失败', HttpStatus.INTERNAL_SERVER_ERROR)
      }
    }
  }

  async updatePassword(passwordDto: UpdateUserPasswordDto) {
    const captcha = await this.redisService.get(`update_password_captcha_${passwordDto.email}`)

    // 验证码验证
    if (!captcha) {
      throw new HttpException('验证码已失效', HttpStatus.BAD_REQUEST)
    }
    if (passwordDto.captcha !== captcha) {
      throw new HttpException('验证码不正确', HttpStatus.BAD_REQUEST)
    }

    // 用户验证
    const foundUser = await this.userRepository.findOneBy({ username: passwordDto.username })
    if (!foundUser) {
      throw new HttpException('用户不存在', HttpStatus.UNAUTHORIZED)
    }
    if (foundUser.email !== passwordDto.email) {
      throw new HttpException('邮箱不正确', HttpStatus.UNAUTHORIZED)
    }

    // 更新用户信息
    foundUser.password = await BcryptPassword.generate(passwordDto.password)

    // 保存
    try {
      await this.userRepository.save(foundUser)
      return '密码修改成功'
    }
    catch (e) {
      if (e instanceof HttpException) {
        // 直接抛出上面的信息
        throw e
      }
      else {
        // 其他错误，记录日志并抛出通用的 HttpException
        this.logger.error(`错误信息: ${e.message}`, UserService.name)
        throw new HttpException('密码修改失败', HttpStatus.INTERNAL_SERVER_ERROR)
      }
    }
  }

  async findUsers(username: string, nickName: string, email: string, pageNo: number, pageSize: number) {
    // 边界条件检查
    if (pageNo <= 0 || pageSize <= 0) {
      throw new BadRequestException('页码和页数最小为 1')
    }

    const skipCount = (pageNo - 1) * pageSize
    const condition: Record<string, any> = {}
    if (username) {
      condition.username = Like(`%${username}%`)
    }
    if (nickName) {
      condition.nickName = Like(`%${nickName}%`)
    }
    if (email) {
      condition.email = Like(`%${email}%`)
    }

    const [users, totalCount] = await this.userRepository.findAndCount({
      select: ['id', 'username', 'nickName', 'email', 'phoneNumber', 'isFrozen', 'headPic', 'updateTime'],
      skip: skipCount,
      take: pageSize,
      where: condition,
      relations: ['roles'],
    })

    const dataList = users.map((item: any) => {
      return {
        ...item,
        roles: item.roles.map((role: any) => role.name),
        updateTime: formatDate(item.updateTime),
      }
    })

    const vo = new UserListVo()
    vo.users = dataList
    vo.totalCount = totalCount

    return vo
  }
}
